// ============================================================================
//
// test_sig.c
// -- tests less's null pointer dereference signal handling  
//
//
// ============================================================================

// bdc: undefine SIM to run as a normal application without the
// simulator. the other defines control which parts of the test run.
//#define SIM
#define TEST_SIGNAL_SYNCHRONOUS
#define TEST_SIGNAL_ASYNCHRONOUS
#define TEST_SIGNAL_NESTED
#define TEST_TRAP

#include "base/simapi.h"

#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

#ifdef __APPLE__
#include <ucontext.h>
#include <mach/ppc/thread_status.h>
#endif // __APPLE__

#ifdef __linux__
#ifdef PPC
#include <ucontext.h>
#endif // PPC
#ifdef i386
#ifdef __USE_GNU
#include <sys/ucontext.h>
#else // !__USE_GNU
#define __USE_GNU
#include <sys/ucontext.h>
#undef __USE_GNU
#endif // !__USE_GNU
#endif // i386
#endif // __linux__

#ifdef _AIX
#include <ucontext.h>
#include <sys/cache.h>
#include <sys/context.h>
#include <sys/mstsave.h>
#endif // _AIX

#ifdef __APPLE__
typedef unsigned int ip;
#else // !__APPLE__
#ifdef __linux__
typedef unsigned long ip;
#else // !__linux__
#ifdef _AIX
typedef unsigned int ip;
#else // !_AIX
#error unsupported OS
#endif // !_AIX
#endif // !__linux__
#endif // !__APPLE__


#ifdef PPC
int instructionLength (ip currentInstruction)
{
    // could use libopcodes, but its always 4 :)
    return 4;
}
#endif // PPC

#ifdef i386
#include "config.h"
// can't run with both libopcodes and simulators -itrace.
// disabled by default just to be safe.
// undef SIM to get update constant if needed.
#if !defined(SIM) && defined(HAVE_LIBOPCODES)
#include <dis-asm.h>
int noop_sprintf (char** bufferPtr, const char* text, ...)
{
    return 0;
}
int instructionLength (ip currentInstruction)
{
    struct disassemble_info info;
    init_disassemble_info(&info, NULL, (fprintf_ftype)noop_sprintf);
    info.mach          = bfd_mach_i386_i386;
    info.buffer        = (void*)currentInstruction;
    info.buffer_vma    =        currentInstruction;
    info.buffer_length = 0xffffffff;
    int length = print_insn_i386(currentInstruction, &info);
    printf("instructionLength(%lx)=%d\n", currentInstruction, length);
    return length;
}
#else // !(!SIM && HAVE_LIBOPCODES)
int instructionLength (ip currentInstruction)
{
    // 7 with -O3
    // 7 with -O2
    // 7 with -O1
    // 3 with -O0
    // 3 with no optimization
    return 7; 
}
#endif // !(!SIM && HAVE_LIBOPCODES)
#endif // i386

ip* printContext (char* message, 
                  int signum, siginfo_t* siginfo, ucontext_t* context)
{
    printf("%s", message);

#ifdef PPC
#ifdef __APPLE__
    ppc_thread_state_t* save = &context->uc_mcontext->ss;
    ip* ptr_ip = &save->srr0;
#else // !__APPLE__
#ifdef __linux__
    struct pt_regs* save = context->uc_mcontext.regs;
    ip* ptr_ip = &save->nip;
#else // !__linux__
#ifdef _AIX
    struct sigcontext* sigcontext = (struct sigcontext*)context;
    struct mstsave *save = &sigcontext->sc_jmpbuf.jmp_context;
    ip* ptr_ip = (uint*)save->iar;
#else // !_AIX
#error unsupported OS
#endif // !_AIX
#endif // !__linux__
#endif // !__APPLE__
#endif // PPC

#ifdef i386
    ip* ptr_ip = (ip*)&context->uc_mcontext.gregs[REG_EIP];
#endif // i386

    unsigned int faultingAddress = (uint)siginfo->si_addr;
    unsigned int instruction   = *((uint*)*ptr_ip);
    fprintf(stdout,"\tmem=0x%08x\n", faultingAddress);
    fprintf(stdout,"\ttrap/exception: type=%d\n", signum);
    fprintf(stdout,"\tip=0x%016lx\n", (long unsigned int)(*ptr_ip));
    fprintf(stdout,"\tinstr=0x%08x\n", instruction);
    fprintf(stdout,"\tcontext=0x%08x\n", (uint)context);        
    return ptr_ip;
}

#ifdef TEST_SIGNAL_SYNCHRONOUS
static uint synchronousTestSigHandlerRun = 0;
void synchronousTestSigHandler(int signum, siginfo_t* siginfo, ucontext_t* context)
{
#ifdef TEST_SIGNAL_NESTED
    assert(synchronousTestSigHandlerRun != 2);
#else // !TEST_SIGNAL_NESTED
    assert(synchronousTestSigHandlerRun != 1);
#endif // !TEST_SIGNAL_NESTED
    ip* ptr_ip = printContext("synchronousTestSigHandler handler called\n", 
                              signum, siginfo, context);
    ip    old = *ptr_ip;
    int delta = instructionLength(*ptr_ip);
    ip    new = old+delta;
    printf("advancing IP from %lx to %lx (at location %p)\n", old, new, ptr_ip);
    (*ptr_ip) = new;

    
#ifdef TEST_SIGNAL_NESTED
    if (synchronousTestSigHandlerRun == 0) {
        synchronousTestSigHandlerRun++;
        char* cp = NULL;
        *cp = 0;
        printf("jumped over the nested null pointer dereference\n");
        assert(synchronousTestSigHandlerRun == 2);
    } else {
#endif // TEST_SIGNAL_NESTED
        synchronousTestSigHandlerRun++;
#ifdef TEST_SIGNAL_NESTED
    }
#endif // TEST_SIGNAL_NESTED
    
#ifdef __APPLE__       
    sigreturn((struct sigcontext*)context);
#endif // !__APPLE__
} 

void testSignalSynchronous () 
{
    struct sigaction action;
    action.sa_flags = SA_RESTART | SA_SIGINFO | SA_NODEFER;
    action.sa_sigaction = (SIGNAL_ACTION)synchronousTestSigHandler;
    if (sigaction(SIGSEGV, &action, NULL) || 
        sigaction(SIGBUS,  &action, NULL)) 
    {
        fprintf(stderr,"sigaction failed\n");
        exit(1);
    }
    printf("signal handler synchronousTestSigHandler=%p registered\n",
           synchronousTestSigHandler);

    char* cp = NULL;
    *cp = 0;
    printf("jumped over the null pointer dereference\n");
#ifdef TEST_SIGNAL_NESTED
    assert(synchronousTestSigHandlerRun == 2);
#else // !TEST_SIGNAL_NESTED
    assert(synchronousTestSigHandlerRun == 1);
#endif // !TEST_SIGNAL_NESTED
}
#endif // TEST_SIGNAL_SYNCHRONOUS

#ifdef TEST_SIGNAL_ASYNCHRONOUS
static int asynchronousTestSigHandlerRun = 0;
void asynchronousTestSigHandler(int signum, siginfo_t* siginfo, ucontext_t* context)
{
    printContext("asynchronousTestSigHandler\n",
                 signum, siginfo, context);
    asynchronousTestSigHandlerRun++;
} 

void testSignalAsynchronous () 
{
    struct sigaction action;
    action.sa_flags = SA_RESTART | SA_SIGINFO | SA_NODEFER;
    action.sa_sigaction = (SIGNAL_ACTION)asynchronousTestSigHandler;
    if (sigaction(SIGALRM, &action, 0)) {
        fprintf(stderr,"sigaction failed\n");
        exit(1);
    }
    printf("signal handler asynchronousTestSigHandler=%p registered\n",
           asynchronousTestSigHandler);

    int loop_count = 0;
    while (TRUE) {
        int result = kill(getpid(), SIGALRM);
        //printf("kill result=%d\n", result);
        assert(result == 0);
        // by running at least twice, we make sure unblock asynchronous signals works
        if (asynchronousTestSigHandlerRun == 2) {
            break;
        }
        loop_count++;
        if (loop_count > 1000) {
            printf("stuck in loop\n");
            assert(FALSE);
        }
    }
    printf("fell out of loop\n");

    // test that failed syscalls work.
    // we test the the success case above.
    int result = kill(getpid(), 99999);
    int error = errno;
    printf("kill result=%d errno=%d\n", result, error);
    assert(result == -1);
    assert(error == EINVAL);
}
#endif // TEST_SIGNAL_ASYNCHRONOUS

#ifdef TEST_SIGNAL_ASYNCHRONOUS
static bool trapHandlerRun = FALSE;
void trapHandler(int signum, siginfo_t* siginfo, ucontext_t* context)
{
    ip* ptr_ip = printContext("trapHandler\n",
                              signum, siginfo, context);

    assert(!trapHandlerRun);
    trapHandlerRun = TRUE;

#ifdef PPC
    // confirm "trap" instruction
    uint32* pc = (uint32*)*ptr_ip;
    assert(*pc == 0x7fe00008);
    int delta = 4;
#endif
#ifdef i386
    char* pc = (char*)*ptr_ip;
    // confirm "int 0x43" instruction
    assert(pc[0] == (char)0xCD);
    assert(pc[1] == (char)0x43);
    int delta = 2;
#endif
    ip    old = *ptr_ip;
    ip    new = old+delta;
    printf("advancing IP from %lx to %lx (at location %p)\n", old, new, ptr_ip);
    (*ptr_ip) = new;
} 

void testTrap () 
{
    struct sigaction action;
    action.sa_flags = SA_RESTART | SA_SIGINFO | SA_NODEFER;
    action.sa_sigaction = (SIGNAL_ACTION)trapHandler;
#ifdef PPC
    int signo = SIGTRAP;
#endif
#ifdef i386
    int signo = SIGSEGV;
#endif
    if (sigaction(signo, &action, 0)) {
        fprintf(stderr,"sigaction failed\n");
        exit(1);
    }
    printf("trap handler trapHandler=%p registered\n",
           trapHandler);

#ifdef PPC
    asm volatile ("trap");
#endif
#ifdef i386
    asm volatile ("int $0x43");
#endif
    assert(trapHandlerRun);
}
#endif // TEST_TRAP

#ifdef SIM
void mainX(int argc, const char** const argv, const char **envp)
#else // !SIM
int main(int argc, char** argv)
#endif // !SIM
{
#ifdef TEST_SIGNAL_SYNCHRONOUS
    testSignalSynchronous();
#endif // TEST_SIGNAL_SYNCHRONOUS

#ifdef TEST_SIGNAL_ASYNCHRONOUS
    testSignalAsynchronous();
#endif // TEST_SIGNAL_ASYNCHRONOUS

#ifdef TEST_TRAP
    testTrap();
#endif // TEST_TRAP

    exit(0);
}
